//
//  LKDownLoadManager.m
//  zlsn
//
//  Created by wangzheng on 2018/4/4.
//  Copyright © 2018年 Luculent. All rights reserved.
//

#import "LKDownLoadManager.h"

#define WZDownloadDirectory [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:NSStringFromClass([self class])]

#define WZFileFullPath(FileName) [WZDownloadDirectory stringByAppendingPathComponent:FileName]

#define WZFilesTotalLengthPlistPath [WZDownloadDirectory stringByAppendingPathComponent:@"FilesTotalLength.plist"] // 存储所有文件大小的plist文件

@interface LKDownLoadManager () <NSURLSessionDelegate, NSURLSessionDataDelegate>

// 存储正在下载 等待下载  以及 暂停 model 的字典
@property (nonatomic, strong) NSMutableDictionary *downloadModelsDic;
@property (nonatomic, strong) NSMutableArray *downloadingModelsArr;
@property (nonatomic, strong) NSMutableArray *waitingModelsArr;

/*
 逻辑:
 (0)下载数组存储正在下载的model(LKDownLoadStateRunning) , 等待数组存储正在等待的model(LKDownLoadStateWaiting)
 (1)在maxConcurrentCount的前提下: 每下载完一个model , 自动获取等待数组中的第一个model进行下载 (先入先出)
 (2)暂停中的model(LKDownLoadStateSuspended)是存储在downloadModelsDic字典中的, 所以它是不会自动触发下载 , 需要手动调取下载方法
 (3)取消/完成/失败的model 会从容器中移除
 */

@end

@implementation LKDownLoadManager

+(void)load {
    
    NSString *cacheDirectory = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:NSStringFromClass([self class])];
    BOOL isDirectory = NO;
    BOOL isExists = [[NSFileManager defaultManager] fileExistsAtPath:cacheDirectory isDirectory:&isDirectory];
    if (!isExists || !isDirectory) {
        [[NSFileManager defaultManager] createDirectoryAtPath:cacheDirectory withIntermediateDirectories:YES attributes:nil error:nil];
    }
    
}


- (void)download:(NSURL *)URL
        filename:(NSString *)fileName
        filesize:(NSInteger)fileSize
           state:(void(^)(LKDownLoadState state))state
        progress:(void(^)(NSInteger receivedSize, NSInteger expectedSize, CGFloat progress))progress
      completion:(void(^)(BOOL isSuccess, NSString *filePath, NSError *error))completion{
    
    if (!URL) { NSLog(@"url不存在");
        return;
    }
    
    if (!fileName) { NSLog(@"fileName不存在");
        return;
    }
    
    if ([self isDownloadCompletedOfFileName:fileName]){
        !state ? : state(LKDownLoadStateCompleted);
        !completion ? : completion(YES, [self fileFullPathOfFileName:fileName], nil);
        return;
    }
    
    LKDownLoadModel *downloadModel = self.downloadModelsDic[fileName];
    
    if (downloadModel) {
        downloadModel.progress = progress;
        downloadModel.completion = completion;
        [self resumeDownloadOfFileName:fileName];
        return;
    }
    
    NSMutableURLRequest *requestM = [NSMutableURLRequest requestWithURL:URL];
    // 在请求头信息中添加需要请求的数据范围（从当前已经下载的数据末尾开始，到整个文件的末尾）
    [requestM setValue:[NSString stringWithFormat:@"bytes=%ld-", (long)[self fileHasDownloadedLengthWithFileName:fileName]] forHTTPHeaderField:@"Range"];
    NSURLSessionDataTask *dataTask = [[NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]
                                                                    delegate:self
                                                               delegateQueue:[[NSOperationQueue alloc] init]] dataTaskWithRequest:requestM];
    dataTask.taskDescription = fileName;
    downloadModel = [[LKDownLoadModel alloc] init];
    downloadModel.dataTask = dataTask;
    downloadModel.outputStream = [NSOutputStream outputStreamToFileAtPath:[self fileFullPathOfFileName:fileName] append:YES];
    downloadModel.URL = URL;
    downloadModel.fileName = fileName;
    downloadModel.totalLength = fileSize;
    downloadModel.state = state;
    downloadModel.progress = progress;
    downloadModel.completion = completion;
    self.downloadModelsDic[dataTask.taskDescription] = downloadModel;
  
    LKDownLoadState downloadState;
    if ([self canResumeDownload]) {
        [self.downloadingModelsArr addObject:downloadModel];
        [dataTask resume];
        downloadState = LKDownLoadStateRunning;
    }else {
        [self.waitingModelsArr addObject:downloadModel];
        downloadState = LKDownLoadStateWaiting;
    }
    
    dispatch_async(dispatch_get_main_queue(), ^{
        if (downloadModel.state) {
            downloadModel.state(downloadState);
        }
    });

}


#pragma mark --- NSURLSession delegate
// 已经接收到反应
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSHTTPURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
    
    LKDownLoadModel *downloadModel = self.downloadModelsDic[dataTask.taskDescription];
    if (!downloadModel) {
        return;
    }
    [downloadModel openOutputStream]; // 开启
    /* 注意: 也可以使用:这种获取文件大小,但需要后台进行http的配置,才可以获取到expectedContentLength . 预期下载的大小+已经下载的大小 = 总大小 这样会更准
    NSInteger totalLength = (long)response.expectedContentLength + [self fileHasDownloadedLengthWithFileName:downloadModel.fileName];
    downloadModel.totalLength = totalLength;
    */
    
    NSMutableDictionary *totalLengthDic = [NSMutableDictionary dictionaryWithContentsOfFile:WZFilesTotalLengthPlistPath] ?: [NSMutableDictionary dictionary];
    totalLengthDic[downloadModel.fileName] = @(downloadModel.totalLength);
    [totalLengthDic writeToFile:WZFilesTotalLengthPlistPath atomically:YES];
    completionHandler(NSURLSessionResponseAllow);
}

// 已经接受到数据
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data{
    
    LKDownLoadModel *downloadModel = self.downloadModelsDic[dataTask.taskDescription];
    if (!downloadModel) {
        return;
    }
    [downloadModel.outputStream write:data.bytes maxLength:data.length]; // 写入文件
    
    dispatch_async(dispatch_get_main_queue(), ^{
        if (downloadModel.progress){
            NSUInteger receivedSize = [self fileHasDownloadedLengthWithFileName:downloadModel.fileName];
            NSUInteger expectedSize = downloadModel.totalLength;
            if (expectedSize == 0) {
                return;
            }
            CGFloat progress = 1.0 * receivedSize / expectedSize;
            downloadModel.progress(receivedSize, expectedSize, progress);
        }
    });

    
    
}

// 已经完成
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{
    
    LKDownLoadModel *downloadModel = self.downloadModelsDic[task.taskDescription];
    if (!downloadModel) {
        return;
    }
    [downloadModel closeOutputStream]; // 关闭
    
    [self.downloadModelsDic removeObjectForKey:task.taskDescription];
    [self.downloadingModelsArr removeObject:downloadModel];
    
    dispatch_async(dispatch_get_main_queue(), ^{
        
        if (error) {
            !downloadModel.completion ? : downloadModel.completion(NO, nil,error);
            !downloadModel.state ? : downloadModel.state(LKDownLoadStateFailed);
        }else{
            !downloadModel.completion ? : downloadModel.completion(YES, WZFileFullPath(downloadModel.fileName),error);
            !downloadModel.state ? : downloadModel.state(LKDownLoadStateCompleted);
        }
    });
    // 开启下一个下载
    [self resumeNextDowloadModel];
}


#pragma mark - Downloads
- (void)suspendDownloadOfFileName:(NSString *)fileName{

    LKDownLoadModel *downloadModel = self.downloadModelsDic[fileName];
    if (!downloadModel) {
        return;
    }
    
    dispatch_async(dispatch_get_main_queue(), ^{
        !downloadModel.state ? : downloadModel.state(LKDownLoadStateSuspended);
    });
    
    if ([self.waitingModelsArr containsObject:downloadModel]) {
        // 加一层保护,其实是走不进来的,能被暂停的肯定是运行中的,但是使用者可能出现失误
        [self.waitingModelsArr removeObject:downloadModel];
    } else {
        [downloadModel.dataTask suspend];
        [self.downloadingModelsArr removeObject:downloadModel];
    }
    [self resumeNextDowloadModel];
}
- (void)suspendAllDownloads{
    
    if (self.downloadModelsDic.count == 0) {
        return;
    }
    
    if (self.waitingModelsArr.count > 0) {
        for (LKDownLoadModel *downloadModel in self.waitingModelsArr) {
            dispatch_async(dispatch_get_main_queue(), ^{
                !downloadModel.state ? : downloadModel.state(LKDownLoadStateSuspended);
            });
        }
        [self.waitingModelsArr removeAllObjects];
    }
    
    if (self.downloadingModelsArr.count > 0) {
        for (LKDownLoadModel *downloadModel in self.downloadingModelsArr) {
            [downloadModel.dataTask suspend];
            dispatch_async(dispatch_get_main_queue(), ^{
                !downloadModel.state ? : downloadModel.state(LKDownLoadStateSuspended);
            });
        }
        [self.downloadingModelsArr removeAllObjects];
    }
    
}

- (void)resumeDownloadOfFileName:(NSString *)fileName{
    
    LKDownLoadModel *downloadModel = self.downloadModelsDic[fileName];
    if (!downloadModel) {
        return;
    }
    
    LKDownLoadState downloadState;
    if ([self canResumeDownload]) {
        [self.downloadingModelsArr addObject:downloadModel];
        [downloadModel.dataTask resume];
        downloadState = LKDownLoadStateRunning;
    } else {
        [self.waitingModelsArr addObject:downloadModel];
        downloadState = LKDownLoadStateWaiting;
    }
    dispatch_async(dispatch_get_main_queue(), ^{
        !downloadModel.state ? : downloadModel.state(downloadState);
    });
}

- (void)resumeAllDownloads{
    
    if (self.downloadModelsDic.count == 0) {
        return;
    }
    
    NSArray *downloadModels = self.downloadModelsDic.allValues;
    for (LKDownLoadModel *downloadModel in downloadModels) {
        LKDownLoadState downloadState;
        if ([self canResumeDownload]) {
            [self.downloadingModelsArr addObject:downloadModel];
            [downloadModel.dataTask resume];
            downloadState = LKDownLoadStateRunning;
        } else {
            [self.waitingModelsArr addObject:downloadModel];
            downloadState = LKDownLoadStateWaiting;
        }
        dispatch_async(dispatch_get_main_queue(), ^{
            !downloadModel.state ? : downloadModel.state(downloadState);
        });
    }
}

- (void)cancelDownloadOfFileName:(NSString *)fileName{
    
    LKDownLoadModel *downloadModel = self.downloadModelsDic[fileName];
    if (!downloadModel) {
        return;
    }
    
    [downloadModel closeOutputStream];
    [downloadModel.dataTask cancel];
    
    dispatch_async(dispatch_get_main_queue(), ^{
        !downloadModel.state ? : downloadModel.state(LKDownLoadStateCanceled);
    });
    
    if ([self.waitingModelsArr containsObject:downloadModel]) {
        [self.waitingModelsArr removeObject:downloadModel];
    } else {
        [self.downloadingModelsArr removeObject:downloadModel];
    }
    [self.downloadModelsDic removeObjectForKey:fileName];
    
    [self resumeNextDowloadModel];
}
- (void)cancelAllDownloads{
    
    if (self.downloadModelsDic.count == 0) {
        return;
    }
    
    NSArray *downloadModels = self.downloadModelsDic.allValues;
    for (LKDownLoadModel *downloadModel in downloadModels) {
        [downloadModel closeOutputStream];
        [downloadModel.dataTask cancel];
        dispatch_async(dispatch_get_main_queue(), ^{
            !downloadModel.state ? : downloadModel.state(LKDownLoadStateCanceled);
        });
    }
    
    [self.waitingModelsArr removeAllObjects];
    [self.downloadingModelsArr removeAllObjects];
    [self.downloadModelsDic removeAllObjects];
}

#pragma mark - File
- (NSString *)fileFullPathOfFileName:(NSString *)fileName{
    return [WZDownloadDirectory stringByAppendingPathComponent:fileName];
}

- (CGFloat)fileHasDownloadedProgressOfFileName:(NSString *)fileName{
    
    if ([self isDownloadCompletedOfFileName:fileName]) {
        return 1.0;
    }
    if ([self totalLength:fileName] == 0) {
        return 0.0;
    }
    return 1.0 * [self fileHasDownloadedLengthWithFileName:fileName] / [self totalLength:fileName];
}

- (void)deleteFile:(NSString *)fileName{
    
    [self cancelDownloadOfFileName:fileName];
    NSMutableDictionary *filesTotalLenth = [NSMutableDictionary dictionaryWithContentsOfFile:WZFilesTotalLengthPlistPath];
    [filesTotalLenth removeObjectForKey:fileName];
    [filesTotalLenth writeToFile:WZFilesTotalLengthPlistPath atomically:YES];
    
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSString *filePath = [WZDownloadDirectory stringByAppendingPathComponent:fileName];
    if (![fileManager fileExistsAtPath:filePath]) {
        return;
    }
    [fileManager removeItemAtPath:filePath error:nil];
}
- (void)deleteAllFiles{
    
    [self cancelAllDownloads];
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSArray *fileNames = [fileManager contentsOfDirectoryAtPath:WZDownloadDirectory error:nil];
    for (NSString *fileName in fileNames) {
        NSString *filePath = [WZDownloadDirectory stringByAppendingPathComponent:fileName];
        [fileManager removeItemAtPath:filePath error:nil];
    }
}


#pragma mark - Assist
- (BOOL)isDownloadCompletedOfFileName:(NSString *)fileName{
    
    if ([self totlaLengthWithFileName:fileName] != 0) {
        if ([self totlaLengthWithFileName:fileName] == [self fileHasDownloadedLengthWithFileName:fileName]) {
            return YES;
        }
    }
    return NO;
}


- (BOOL)canResumeDownload {
    
    if (self.maxConcurrentCount == -1) {
        return YES;
    }
    if (self.downloadingModelsArr.count >= self.maxConcurrentCount) {
        return NO;
    }
    return YES;
}

- (NSInteger)totalLength:(NSString *)fileName {
    
    NSDictionary *filesTotalLenth = [NSDictionary dictionaryWithContentsOfFile:WZFilesTotalLengthPlistPath];
    if (!filesTotalLenth) {
        return 0;
    }
    if (!filesTotalLenth[fileName]) {
        return 0;
    }
    return [filesTotalLenth[fileName] integerValue];
}

- (void)resumeNextDowloadModel {
    
    if (self.maxConcurrentCount == -1) { // no limit so no waiting for download models
        return;
    }
    
    if (self.waitingModelsArr.count == 0) {
        return;
    }
    // 先入先出
    LKDownLoadModel *downloadModel = self.waitingModelsArr.firstObject;
    [self.waitingModelsArr removeObject:downloadModel];
    
    LKDownLoadState downloadState;
    if ([self canResumeDownload]) {
        [self.downloadingModelsArr addObject:downloadModel];
        [downloadModel.dataTask resume];
        downloadState = LKDownLoadStateRunning;
    }else {
        [self.waitingModelsArr addObject:downloadModel];
        downloadState = LKDownLoadStateWaiting;
    }
    dispatch_async(dispatch_get_main_queue(), ^{
        !downloadModel.state ? : downloadModel.state(downloadState);
    });
    
}





// 根据文件名称返回该文件的总大小
- (NSInteger)totlaLengthWithFileName:(NSString *)fileName {
    
    NSDictionary *totalLengthDic = [NSDictionary dictionaryWithContentsOfFile:WZFilesTotalLengthPlistPath];
    if (!totalLengthDic) {
        return 0;
    }
    if (!totalLengthDic[fileName]) {
        return 0;
    }
    return [totalLengthDic[fileName] integerValue];
    
}
// 根据文件名称获取到沙盒中取出文件,并返回已经下载的文件大小
- (NSInteger)fileHasDownloadedLengthWithFileName:(NSString *)fileName {
    
    NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:WZFileFullPath(fileName) error:nil];
    if (!fileAttributes) {
        return 0;
    }
    return [fileAttributes[NSFileSize] integerValue];
    
}


#pragma mark --- Singleton
+ (instancetype)sharedManager{
    
    static LKDownLoadManager *downloadManager = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        downloadManager = [[LKDownLoadManager alloc] init];
        downloadManager.maxConcurrentCount = 1;
    });
    return downloadManager;
    
}

#pragma mark --- lazy init
- (NSMutableDictionary *)downloadModelsDic{
    if (!_downloadModelsDic) {
        _downloadModelsDic = [NSMutableDictionary dictionary];
    }
    return _downloadModelsDic;
}

- (NSMutableArray *)downloadingModelsArr {
    
    if (!_downloadingModelsArr) {
        _downloadingModelsArr = [NSMutableArray array];
    }
    return _downloadingModelsArr;
}

- (NSMutableArray *)waitingModelsArr {
    
    if (!_waitingModelsArr) {
        _waitingModelsArr = [NSMutableArray array];
    }
    return _waitingModelsArr;
}

@end
